package edu.mtu.citizenscience.ethnography.uploader;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import org.json.JSONException;
import org.json.JSONObject;

import edu.mtu.citizenscience.ethnography.util.Util;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

public class EthnographyUploadService extends IntentService {

	private static final String LOG_TAG = "EthnographyUploadService";

	static final String API_KEY_KEY = "X-API-KEY";

	public static final String UPLOAD_SERVICE = "uploadService";

	public static final String OUTPUT_RESULT = "result";
	public static final String RESULT_LOCAL_FAILURE = "local failure";
	public static final String RESULT_SUCCESS = "success";
	public static final String RESULT_CONTINUE = "continue";
	public static final String CONTINUE_SENDTYPE = "sendtype";
	public static final String SENDTYPE_INTERVIEW = "interview";
	public static final String SENDTYPE_AUDIO = "audio";
	public static final String SENDTYPE_PHOTO = "photo";
	public static final String CONTINUE_CURRENT = "current";
	public static final String CONTINUE_TOTAL = "total";
	public static final String RESULT_ERROR = "error";
	public static final String RESULT_FAILURE = "failure";
	public static final String RESULT_NO_RESPONSE = "no response";

	public static final String INPUT_METHOD = "method";

	public static final String METHOD_UPLOAD = "upload";
	public static final String METHOD_LOGIN = "login"; 
	public static final String METHOD_REGISTER = "register";

	public static final String INPUT_USERNAME = "USERNAME";
	public static final String INPUT_PASSWORD = "PASSWORD";
	public static final String INPUT_FIRST_NAME = "FIRST_NAME";
	public static final String INPUT_LAST_NAME = "LAST_NAME";
	public static final String INPUT_EMAIL = "EMAIL";
	public static final String INPUT_ID = "id";
	
	private static final long MAX_FILE_SIZE = 5242880; // 5 megabytes

	private static Map<String, Class<? extends EthnographyUploaderAction>> actions;
	
	static {
		actions = new HashMap<String, Class<? extends EthnographyUploaderAction>>();
		actions.put(METHOD_LOGIN, EthnographyLoginAction.class);
		actions.put(METHOD_REGISTER, EthnographyRegisterAction.class);
		actions.put(METHOD_UPLOAD, EthnographyUploadAction.class);
	}

	//private static final String API_URL = "http://citizenscience.cs.mtu.edu/ethnography/prod/index.php/API";
	private static final String API_URL = "http://citizenscience.cs.mtu.edu/ethnography/dev/index.php/API";
	
	private LocalBroadcastManager broadcastManager;

	public EthnographyUploadService() {
		super("EthnographyUploadService");
		broadcastManager = LocalBroadcastManager.getInstance(this);
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		String actionName = intent.getStringExtra(INPUT_METHOD);
		EthnographyUploaderAction action = getAction(actionName);
		if (action == null) {
			stopSelf();
		}
		synchronized (this) {
			doRequest(action, intent);
		}
		stopSelf();
	}

	private EthnographyUploaderAction getAction(String actionName) {
		EthnographyUploaderAction action = null;
		if (actionName != null) {
			Class<? extends EthnographyUploaderAction> theClass = actions.get(actionName);
			if (theClass != null) {
				Constructor<?> constructors[] = theClass.getConstructors();
				try {
					action = (EthnographyUploaderAction) constructors[0].newInstance(this);
				} catch (Exception e) {
					return null;
				} 
			}
		}
		return action;
	}

	private void doRequest(EthnographyUploaderAction action, Intent intent) {
		while (action.hasNext()) {
			Map<String, Object> params = action.getParams(intent);
			String resource = action.getResource();
			EthnographyResponseHandler handler = action.getHandler();
			if (params == null || resource == null || handler == null) {
				// Something wrong with the params; send a broadcast and stop
				Intent result = new Intent();
				result.putExtra(OUTPUT_RESULT, RESULT_LOCAL_FAILURE);
				broadcastManager.sendBroadcast(result);
				stopSelf();
			}

			String url = API_URL + "/" + resource;
			try {
				if (params != null && params.containsKey(EthnographyUploadAction.FILE)) {
					sendFile(new URL(url), 
							(File) params.get(EthnographyUploadAction.FILE), 
							(String) params.get(EthnographyUploadAction.FILENAME),
							(Integer) params.get(EthnographyUploadAction.SOUT_ID),
							new EthnographyCallback(handler));
				} else {
					sendJSON(new URL(url), new JSONObject(params), new EthnographyCallback(handler));
				}
			} catch (MalformedURLException e) {
				Intent result = new Intent(EthnographyUploadService.UPLOAD_SERVICE);
				result.putExtra(OUTPUT_RESULT, RESULT_LOCAL_FAILURE);
				broadcastManager.sendBroadcast(result);
				stopSelf();
			}
		}
	}

	private void sendJSON(URL url, JSONObject params, EthnographyCallback callback) {
		String json = null;
		int responseCode = EthnographyCallback.STATUS_OTHER;
		try {
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();

			// Set up the request
			String charset = "UTF-8";

			SharedPreferences prefs = getSharedPreferences(Util.PREFS_KEY, Context.MODE_PRIVATE);
			String apiKey = prefs.getString(Util.PREFS_API_KEY, Util.API_KEY_DEFAULT);

			connection.addRequestProperty(API_KEY_KEY, apiKey);
			connection.setRequestProperty("Content-Type", "application/json");
			connection.setRequestProperty("Accept-Charset", charset);
			connection.setRequestProperty("Cookie", "ce5d268ed378c99451cdc6a7efe47936; ci_session=a%3A5%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%22488ba31ce193a998633a27c7091e6928%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A12%3A%2271.13.214.79%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A109%3A%22Mozilla%2F5.0+%28Windows+NT+6.1%3B+WOW64%29+AppleWebKit%2F537.22+%28KHTML%2C+like+Gecko%29+Chrome%2F25.0.1364.172+Safari%2F537.22%22%3Bs%3A13%3A%22last_activity%22%3Bi%3A1363733420%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7D90374fc01b2d2443946189b869ffabdf");

			// Sets post data if doing a POST
			if (params.length() > 0)
			{
				connection.setDoOutput(true);
				OutputStream output = null;
				try {
					output = connection.getOutputStream();
					String jsonString = params.toString();
					output.write(jsonString.getBytes(charset));
				} finally {
					if (output != null) {
						output.close();
					}
				}
			}

			// Receive the request (attempting to read response should automatically trigger send)
			responseCode = connection.getResponseCode();

			// Copied from http://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests
			String contentType = connection.getHeaderField("Content-Type");
			for (String param : contentType.replace(" ", "").split(";")) {
				if (param.startsWith("charset=")) {
					charset = param.split("=", 2)[1];
					break;
				}
			}

			if (charset != null) {
				InputStream response = connection.getInputStream();
				BufferedReader reader = null;
				json = "";
				try {
					reader = new BufferedReader(new InputStreamReader(response, charset));
					for (String line; (line = reader.readLine()) != null;) {
						json += line;
					}
				} finally {
					if (reader != null) {
						reader.close();
					}
				}
				JSONObject resultJson = new JSONObject(json);
				callback.callback(url.toExternalForm(), resultJson, responseCode);
			}
		} catch (UnknownHostException e) {
			callback.callback(url.toExternalForm(), null, EthnographyCallback.STATUS_NORESPONSE);
		} catch (IOException e) {
			callback.callback(url.toExternalForm(), null, responseCode);
		} catch (JSONException e) {
			Log.d(LOG_TAG, "Could not parse response text:\n" + json);
			callback.callback(url.toExternalForm(), null, EthnographyCallback.STATUS_SERVER_BAD_REQUEST);
		}
	}

	private void sendFile(URL url, File file, String filename, int id, EthnographyCallback callback) {
		String json = null;
		int responseCode = EthnographyCallback.STATUS_OTHER;
		String boundaryLabel = "WebKitFormBoundary" + Long.toHexString(System.currentTimeMillis());
		String CRLF = "\r\n";
		
		if (file.length() > MAX_FILE_SIZE) {
			// Stop if the file is too large
			Log.e(LOG_TAG, "File size is " + file.length() + " bytes; max is " + MAX_FILE_SIZE);
			Intent result = new Intent(EthnographyUploadService.UPLOAD_SERVICE);
			result.putExtra(OUTPUT_RESULT, RESULT_LOCAL_FAILURE);
			broadcastManager.sendBroadcast(result);
			stopSelf();
		}
		
		try {
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setDoOutput(true);

			// Set up the request
			String HTTPcharset = "ISO-8859-1,utf-8;q=0.7,*;q=0.3";
			String writerCharset = "utf-8";

			SharedPreferences prefs = getSharedPreferences(Util.PREFS_KEY, Context.MODE_PRIVATE);
			String apiKey = prefs.getString(Util.PREFS_API_KEY, Util.API_KEY_DEFAULT);

			connection.addRequestProperty(API_KEY_KEY, apiKey);
			connection.setRequestProperty("Content-Type", 
					"multipart/form-data; boundary=" + boundaryLabel);

			connection.setRequestProperty("Accept", "*/*");
			connection.setRequestProperty("Accept-Encoding", "gzip,deflate,sdch");
			connection.setRequestProperty("Accept-Language", "en-US,en;q=0.8");
			connection.setRequestProperty("Accept-Charset", HTTPcharset);

			PrintWriter writer = null;
			try {
				// Copied from http://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests
				OutputStream output = connection.getOutputStream();
				writer = new PrintWriter(new OutputStreamWriter(output, writerCharset), true);

				writer.append("--").append(boundaryLabel).append(CRLF);
				writer.append("Content-Disposition: form-data; name=\"userfile\"; filename=\"" + filename + "\"").append(CRLF);
				String contentType = URLConnection.guessContentTypeFromName(file.getName());
				if (contentType.equals("video/mp4")) {
					// This bit is here to correct for wrong guesses
					contentType = "audio/mp4";
				}
				writer.append("Content-Type: " + contentType).append(CRLF);
				writer.append("Content-Transfer-Encoding: binary").append(CRLF);
				writer.append(CRLF).flush();
				InputStream input = null;
				try {
					input = new FileInputStream(file);
					// Write the file itself to the request
					byte[] buffer = new byte[1024];
					for (int length = 0; (length = input.read(buffer)) > 0;) {
						output.write(buffer, 0, length);
					}
					writer.append(CRLF).append("--").append(boundaryLabel).append(CRLF);
					// Write the server key
					writer.append("Content-Disposition: form-data; name=\"storyID\"").append(CRLF).append(CRLF);
					writer.append(String.valueOf(id));
					writer.append(CRLF).append("--").append(boundaryLabel).append("--").append(CRLF);
					output.flush(); // Important! Output cannot be closed. Close of writer will close output as well.
				} finally {
					if (input != null) 
						try { 
							input.close(); 
						} catch (IOException logOrIgnore) {
							callback.callback(url.toExternalForm(), null, 0);
						}
				}
			} finally {
				if (writer != null) {
					writer.close();
				}
			}
			
			// Receive the request (attempting to read response should automatically trigger send)
			responseCode = connection.getResponseCode();
			
			// Copied from http://stackoverflow.com/questions/2793150/how-to-use-java-net-urlconnection-to-fire-and-handle-http-requests
			String contentType = connection.getHeaderField("Content-Type");
			String charset = null;
			for (String param : contentType.replace(" ", "").split(";")) {
				if (param.startsWith("charset=")) {
					charset = param.split("=", 2)[1];
					break;
				}
			}

			if (charset == null) {
				charset = writerCharset;
			}
				InputStream response = connection.getInputStream();
				BufferedReader reader = null;
				json = "";
				try {
					reader = new BufferedReader(new InputStreamReader(response, charset));
					for (String line; (line = reader.readLine()) != null;) {
						json += line;
					}
				} finally {
					if (reader != null) {
						reader.close();
					}
				}
				JSONObject resultJson = new JSONObject(json);
				callback.callback(url.toExternalForm(), resultJson, responseCode);
		} catch (UnknownHostException e) {
			callback.callback(url.toExternalForm(), null, EthnographyCallback.STATUS_NORESPONSE);
		} catch (IOException e) {
			Log.e(LOG_TAG, e.getLocalizedMessage(), e);
			callback.callback(url.toExternalForm(), null, responseCode);
		} catch (JSONException e) {
			Log.d(LOG_TAG, "Could not parse response text:\n" + json);
			callback.callback(url.toExternalForm(), null, EthnographyCallback.STATUS_SERVER_BAD_REQUEST);
		}
	}

}
